#ifndef __M_SESSION_H__
#define __M_SESSION_H__

#include "util.hpp"
#include "logger.hpp"
#include <memory>
#include <mutex>
#include <unordered_map>
#include <functional>

// 首先设计session类
// 类成员包括
// 1. ssid(会话id)
// 2. uid(用户id)
// 3. state(用户状态)
// 4. 会话所绑定的定时器对象

typedef websocketpp::server<websocketpp::config::asio> wsserver_t;

typedef enum
{
    UNLOGIN,
    LOGIN
} ss_state;

class session
{
public:
    session(int ssid)
        : _ssid(ssid)
    {
        DLOG("SESSION %p created!", this);
    }

    ~session()
    {
        DLOG("SESSION %p released!", this);
    }

    // 哪个用户关联这个session，我们需要设置
    void set_user(int uid)
    {
        _uid = uid;
    }

    // 获取这个session关联了哪一个uid
    int get_uid()
    {
        return _uid;
    }

    // 判断是否处于登录状态
    bool is_login()
    {
        return _state == LOGIN;
    }

    // 设置定时器(任务)
    void set_timer(const wsserver_t::timer_ptr &tp)
    {
        _tp = tp;
    }

    // 获取定时器对象
    wsserver_t::timer_ptr &get_timer()
    {
        return _tp;
    }

    // 设置这个session会话的用户状态(其实状态这个成员变量可以不用维护，因为只有登录成功了的用户才有会话)
    void set_state(ss_state state)
    {
        _state = state;
    }

    // 返回会话id
    int ssid()
    {
        return _ssid;
    }

private:
    int _ssid;                 // 标识符
    int _uid;                  // session对应的用户ID
    ss_state _state;           // 用户状态：未登录，已登录
    wsserver_t::timer_ptr _tp; // session关联的定时器
};

// 下面我们需要一个管理session的类，把上面创建的一个一个session对象管理起来
// 1. 所以就需要一个unordered_map进行管理
// 2. 每个session都应该被分配一个session id，所以session_manager的成员变量中还要有一个_next_ssid分配器，用于给每个session分配唯一的ssid
// 3. 因为需要对上面两个共享资源访问，所以需要一把锁
// 4. 每个sessio都需要一个定时任务，判断该会话是否应该被销毁，所以需要一个websocket::server对象用于获取set_timer()API，设置定时任务

// 我们还预定义了两个宏出来，分别代表session此刻是永久存在、session的过期销毁时间，过期销毁时间的初始值设置为了30000ms
#define SESSION_TIMEOUT 30000
#define SESSION_PERMANENT -1

using session_ptr = std::shared_ptr<session>;

class session_manager
{
public:
    session_manager(wsserver_t *svr)
        : _next_ssid(1), _svr(svr)
    {
        DLOG("session 管理模块初始化完毕!");
    }
    ~session_manager()
    {
        DLOG("session 管理模块销毁完毕!");
    }

    // 创建会话，并添加到unordered_map中
    session_ptr create_session(int uid, ss_state state)
    {
        std::unique_lock<std::mutex> lock(_mtx);

        session_ptr sp(new session(_next_ssid));

        sp->set_state(LOGIN);
        sp->set_user(uid);

        _sessions.insert(std::make_pair(_next_ssid, sp));
        _next_ssid++;
        return sp;
    }

    // 通过ssid，返回session的详细信息(shared_ptr<session>)
    session_ptr get_session_by_ssid(int ssid)
    {
        std::unique_lock<std::mutex> lock(_mtx);

        auto iter = _sessions.find(ssid);
        if (iter == _sessions.end())
            return session_ptr();
        return iter->second;
    }

    // 销毁session，只需移除，对应的session_ptr会自动销毁，然后以RAII风格的方式自动释放堆上的空间
    void destroy_session(int ssid)
    {
        std::unique_lock<std::mutex> lock(_mtx);
        _sessions.erase(ssid);
    }

    void set_session_expire_time(int ssid, int ms)
    {
        // 暂时获取保存该会话句柄
        session_ptr sp = get_session_by_ssid(ssid);
        if (sp.get() == nullptr)
            return; // session不存在

        // 下面有四种状态的切换
        wsserver_t::timer_ptr tp = sp->get_timer(); // 获取定时器对象
        if (tp.get() == nullptr && ms == SESSION_PERMANENT)     // 永久存在改为永久存在(没变)
        {
            // tp本来就为空，说明没有定时器->会话永远存在 && ms还是设置永久存在，所以不用发生切换
            return;
        }

        if (tp.get() == nullptr && ms == SESSION_TIMEOUT)       // 永久存在改为定时删除
        {
            // tp本来就为空，说明没有定时器->会话永远存在 && ms设置定时任务，所以要更改定时器任务，把永久存在设为定时删除
            wsserver_t::timer_ptr tmp = _svr->set_timer(ms, std::bind(&session_manager::destroy_session, this, ssid));
            sp->set_timer(tmp);
            return;
        }

        if (tp.get() != nullptr && ms == SESSION_PERMANENT)     // 定时删除更改为永久存在
        {
            // tp本来不为空->设有定时任务 && ms表明需要更改为永久存在，即删除定时器
            // 但是删除定时器就会立马触发一次定时器任务执行
            // 所以需要删除之后重新保存由于刚刚删除定时器触发的任务的session
            // 但是不能保证是上面两个步骤的顺序，如果是先执行了定时器任务，那再次插入就没问题，如果先插入了，定时器任务才执行，那会话就被干掉了
            // 所以也需要把重新保存会话这个任务也设置成定时器任务，并且立即执行
            tp->cancel();   //这个取消定时任务并不是立即取消的

            _svr->set_timer(0, std::bind(&session_manager::append_already_session, this, ssid, sp));
            sp->set_timer(wsserver_t::timer_ptr());
        }

        if (tp.get() != nullptr && ms == SESSION_TIMEOUT)       // 刷新/重置定时器时间
        {
            // tp本来不为空->设有定时任务 && ms表明需要更改为定时任务
            // 步骤和上面类似
            // 1. 取消原有任务，那这会触发定时器任务从而删掉会话
            tp->cancel();   //这个取消定时任务并不是立即取消的

            // 2. 重新保存刚刚被删掉的会话
            _svr->set_timer(0, std::bind(&session_manager::append_already_session, this, ssid, sp));

            // 3. 重新设置定时器时间，此会话依然是一个定时删除
            wsserver_t::timer_ptr tmp = _svr->set_timer(ms, std::bind(&session_manager::destroy_session, this, ssid));
            sp->set_timer(tmp);
        }
    }

    void append_already_session(int ssid, session_ptr sp)
    {
        // 把已有的会话重新保存放到_sessions中
        std::unique_lock<std::mutex> lock(_mtx);
        _sessions.insert(std::make_pair(ssid, sp));
    }

private:
    std::unordered_map<int, session_ptr> _sessions;
    int _next_ssid;
    std::mutex _mtx;
    wsserver_t *_svr;
};

#endif